﻿using System;
using System.Linq;
using System.Xml.Serialization;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Collections.Specialized;
using System.ComponentModel;
using PasswordProvider.ObjectModel.Properties;

namespace PasswordProvider.ObjectModel
{
    /// <summary>
    /// List Of Sites with global encryption data included. 
    /// </summary>
    [DataContract]
    public sealed class PasswordProviderData : DataObject
    {
        #region Construction

        /// <summary>
        /// Private constructor to enforce singleton pattern. (Note that since DataContractorSerializer doesn't 
        /// call the constructor, this has no effect on deserialization.)
        /// </summary>
        private PasswordProviderData()
        {
            Initialize();
        }

        /// <summary>
        /// Necessary because DataContractSerializer does not call the constructor. Must call Initialized manually
        /// after object has been deserialized.
        /// </summary>
        public void Initialize()
        {
            Settings.Default.PropertyChanged += new PropertyChangedEventHandler(Settings_PropertyChanged);
        }

        #endregion

        #region Static (Factory) Public Interface

        /// <summary>
        /// Event to signal when the value of Current changes.
        /// </summary>
        public static event EventHandler CurrentChanged;

        /// <summary>
        /// The single instance of ProtectedSiteCollection.
        /// </summary>
        public static PasswordProviderData Current
        {
            get { return _current; }
            private set
            {
                if (_current != value)
                {
                    _current = value;
                    if (CurrentChanged != null)
                        CurrentChanged(null, new EventArgs());
                }
            }
        } private static PasswordProviderData _current;

        /// <summary>
        /// Opens the file at the location specified by ActiveFilePath, creats an instance of ProtectedSiteCollection
        /// and assigns the new object to singleton ProtectedSiteCollection.Current. Returns true if successful, false otherwise.
        /// </summary>
        public static bool Open(ProtectedString password)
        {
            return Open(password, Settings.Default.ActiveFilePath);
        }
        /// <summary>
        /// Opens the file at the specified location, creats an instance of ProtectedSiteCollection
        /// and assigns the new object to singleton ProtectedSiteCollection.Current. Returns true if successful,
        /// false otherwise. 
        /// </summary>
        public static bool Open(ProtectedString password, string path)
        {
            Cipher cipher = new Cipher();
            PasswordProviderData collection = FileHelper.Open(path, password, cipher);
            if (collection != null)
            {
                collection.Initialize();
                if (cipher.IsAuthenticated)
                    collection.Cipher = cipher;
                else
                {
                    if (!collection.Cipher.Authenticate(password))
                        return false;
                }
                Settings.Default.ActiveFilePath = path;
                Current = collection;
                Settings.Default.Save();

                return true;
            }

            return false;
        }

        /// <summary>
        /// Creates a new instance of ProtectedSiteCollection and assigns it to the ProtectedSiteCollection.Current singleton property.
        /// </summary>
        public static PasswordProviderData New(ProtectedString password, int keyGenerationIterations, string path)
        {
            PasswordProviderData collection = new PasswordProviderData();
            collection.Cipher.SetPassword(password, keyGenerationIterations);
            Settings.Default.ActiveFilePath = path;
            FileHelper.Save(collection);
            Current = collection;
            Settings.Default.ActiveFilePath = path;
            Settings.Default.Save();
            return Current;
        }

        #endregion // Static (Factory) Interface

        #region Data Members (Internal and Public)

        [DataMember]
        public SiteCollection Sites
        {
            get
            {
                if (_sites == null)
                {
                    Sites = new SiteCollection();
                }
                return _sites; 
            }
            private set
            {
                _sites = value;
                _sites.SecurityManager = this.SecurityManager;
                _sites.CollectionChanged += delegate { FileHelper.Save(); };                
            }
        } private SiteCollection _sites;


        public int MasterKeyGenerationIterations
        {
            get { return this.Cipher.MasterKeyGenerationIterations; }
        }

        /// <summary>
        /// Provides access to the Cipher used to encrypt and decrypt values in this collection.         
        /// Accessed internally by FileHelper. 
        /// </summary>
        [DataMember]
        internal Cipher Cipher
        {
            get
            {
                if (_cipher == null)
                    _cipher = new Cipher();
                return _cipher;
            }
            set { _cipher = value; }
        } private Cipher _cipher;


        #endregion // Internal Interface

        #region Public Interface

        public void Save(string path)
        {
            FileHelper.Save(this, path);
        }

        public void Backup()
        {
            FileHelper.Backup(this);
        }

        public SecurityManager SecurityManager
        {
            get 
            {
                if (_securityManager == null)
                {
                    _securityManager = new SecurityManager(Cipher);
                }
                return _securityManager;
            }
        } private SecurityManager _securityManager;

        /// <summary>
        /// Change the master password for the entire file
        /// </summary>
        public void ChangePassword(ProtectedString newPassword, int iterations)
        {
            Cipher newCipher = new Cipher();
            newCipher.SetPassword(newPassword, iterations);

            foreach (EncryptedValue encryptedValue in GetAllEncryptedValues())
            {
                if (encryptedValue != null && encryptedValue.HasValue)
                {
                    ProtectedString unencrypted = SecurityManager.GetValueDecrypted(encryptedValue, false);
                    EncryptedValue newEncrypted = new EncryptedValue();
                    SecurityManager.SetValueEncrypted(unencrypted, newEncrypted);
                }
            }

            FileHelper.Save();

        }

        #endregion // Public Interface

        #region Private Helpers

        void Settings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            switch(e.PropertyName)
            {
                case "EncryptDataFile":
                    FileHelper.Save(this);
                    break;
            }
        }


        /// <summary>
        /// Obtain a flat list of all the encrypted values contained by the current sites collection.        
        /// </summary>
        private List<EncryptedValue> GetAllEncryptedValues()
        {
            // Use a little LINQ for this. Isn't it nice!
            var query = from site in this.Sites
                        from prop in site.GetType().GetProperties()
                        where prop.PropertyType == typeof(EncryptedValue)
                        select prop.GetValue(site, null) as EncryptedValue;

            // weed out null values. TODO: There has got to be a way to do this in the main query.
            var k = from e in query where e != null select e;

            return k.ToList();
        }

        #endregion

    }
}
